﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ShevaEngine.Core.Modules.Graphics.Lights;
using ShevaEngine.Core.Modules.Materials;
using ShevaEngine.Core.Modules.Scenes;

namespace ShevaEngine.Core.Modules.Graphics.ShadowMaps
{
    /// <summary>
    /// Dual paraboloid shadow maps.
    /// </summary>
    public class DualParaboloidShadowMaps : ShadowMap
    {
        /// <summary>Shadow map.</summary>
        public RenderTarget2D ShadowMapTop;
        /// <summary>Shadow map.</summary>
        public RenderTarget2D ShadowMapBottom;
        /// <summary>Shadow map size.</summary>
        public Int32 ShadowMapSize;
        /// <summary>Light.</summary>
        public Light Light;
        /// <summary>Scene.</summary>
        public Scene Scene;
        /// <summary>Light camera.</summary>
        private Matrix LightViewCamera;
        /// <summary>Shadow map effect.</summary>
        private Effect ShadowMapEffect;
        /// <summary>Last draw call.</summary>
        private DrawCallKey LastDrawCallKey;
        /// <summary>Initial draw call.</summary>
        private DrawCallKey InitialDrawCallKey;

        /// <summary>
        /// Constructor.
        /// </summary>
        public DualParaboloidShadowMaps(Light light, Scene scene)
        {
            this.ShadowMapSize = 512;

            this.ShadowMapTop = new RenderTarget2D(ShevaEngine.Instance.GraphicsDevice,
                ShadowMapSize, ShadowMapSize, false, SurfaceFormat.Single, DepthFormat.Depth24Stencil8);
            this.ShadowMapBottom = new RenderTarget2D(ShevaEngine.Instance.GraphicsDevice,
                ShadowMapSize, ShadowMapSize, false, SurfaceFormat.Single, DepthFormat.Depth24Stencil8);

            this.Light = light;
            this.Scene = scene;            

            this.ShadowMapEffect = MaterialManager.Instance.GetEffect("DualParaboloidSM");

            this.InitialDrawCallKey = new DrawCallKey(
                UInt16.MaxValue, UInt16.MaxValue, UInt16.MaxValue, UInt16.MaxValue);
        }


        /// <summary>
        /// Method creates shadow map.
        /// </summary>
        public override void CreateShadowMap()
        {
            ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(this.ShadowMapTop);

            ShevaEngine.Instance.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.White, 1.0f, 1);

            ShevaEngine.Instance.GraphicsDevice.BlendState = BlendState.Opaque;
            //GraphicsEngine.Instance.GraphicsDevice.RenderState.AlphaBlendEnable = false;            

            DepthStencilState depthStencilState = new DepthStencilState();
            depthStencilState.DepthBufferEnable = true;
            depthStencilState.DepthBufferWriteEnable = true;
            depthStencilState.DepthBufferFunction = CompareFunction.Less;
            ShevaEngine.Instance.GraphicsDevice.DepthStencilState = depthStencilState;

            {
                RasterizerState rasterizerState = new RasterizerState();
                rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;                
                ShevaEngine.Instance.GraphicsDevice.RasterizerState = rasterizerState;
            }

            RenderingPipeline pipeline = this.Scene.GetRenderingPipeline(this.LightViewCamera);

            this.LightViewCamera = Matrix.CreateLookAt(
                this.Light.World.Translation, this.Light.World.Translation - new Vector3(0, 1, 0), Vector3.UnitZ);

            this.ShadowMapEffect.Parameters["ShadowMapFarPlane"].SetValue(this.Light.Radius);
            this.ShadowMapEffect.Parameters["View"].SetValue(this.LightViewCamera);

            this.ShadowMapEffect.Parameters["ShadowMapDirection"].SetValue(1.0f);

            this.ShadowMapEffect.CurrentTechnique.Passes[0].Apply();

            this.RenderPipeline(pipeline);

            ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(this.ShadowMapBottom);

            ShevaEngine.Instance.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.White, 1.0f, 1);

            {
                RasterizerState rasterizerState = new RasterizerState();
                rasterizerState.CullMode = CullMode.CullClockwiseFace;                
                ShevaEngine.Instance.GraphicsDevice.RasterizerState = rasterizerState;
            }

            this.ShadowMapEffect.Parameters["ShadowMapDirection"].SetValue(-1.0f);

            this.RenderPipeline(pipeline);

            ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(null);            
        }        
        
        /// <summary>
        /// Render pipeline method.
        /// </summary>
        private void RenderPipeline(RenderingPipeline pipeline)
        {
            this.LastDrawCallKey = this.InitialDrawCallKey;

            foreach (KeyValuePair<DrawCallKey, List<DrawCallPipeline>> item in pipeline.OpaqueObjects)
            {
                if (item.Key.TextureState != this.LastDrawCallKey.TextureState)
                {
                    TextureState textureState = TextureManager.Instance[item.Key.TextureState];

                    //if (textureState.ContainsKey("Diffuse"))
                    //    this.ShadowMapEffect.Parameters["Diffuse"].SetValue(textureState["Diffuse"]);
                }

                this.Scene.GraphicsBuffersManager.SetBuffers(item.Key.BuffersID);

                this.RenderDrawCalls(item.Value);
            }            
        }

        /// <summary>
        /// Method renders draw call.
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="worldMatrices"></param>
        private void RenderDrawCalls(List<DrawCallPipeline> pipeline)
        {
            for (int drawCallIndex = 0; drawCallIndex < pipeline.Count; drawCallIndex++)
            {
                int matricesCount = pipeline[drawCallIndex].WorldMatrices.Count;

                for (int i = 0; i < matricesCount; i++)
                {
                    this.ShadowMapEffect.Parameters["World"].SetValue(pipeline[drawCallIndex].WorldMatrices[i]);                    
                    
                    this.ShadowMapEffect.CurrentTechnique.Passes[0].Apply();

                    pipeline[drawCallIndex].DrawCall.Render();
                }
            }
        }
    }
}
